package controllers;

import helpers.ConsoleUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import be.objectify.deadbolt.java.actions.Group;
import be.objectify.deadbolt.java.actions.Restrict;

import models.Company;
import models.Project;
import models.Resource;
import models.ResourceType;
import models.TestSuiteWizard;
import models.TestSuiteWizard.Step1;
import models.TestSuiteWizard.Step2;
import models.TestSuiteWizard.Step3;
import play.data.DynamicForm;
import play.data.Form;
import play.mvc.Controller;
import play.mvc.Http.Session;
import play.mvc.Result;
import services.ConfigurationService;
import services.TestSuiteService;
import views.html.testsuites.*;
import views.html.testsuites.wizard.*;

public class TestSuites extends Controller {

	private static final ConfigurationService configurationService = ConfigurationService.getService();
	private static final TestSuiteService testSuiteService = TestSuiteService.getService();

	// private static final TestCaseService testCaseService = TestCaseService.getService();

	private enum StepDirection {
		Next, Previous
	}

	public static final String[] TEST_SUITE_HTML_FILE_EXTENSIONS = new String[] { ".htm", ".html", ".xhtml" };
	public static final String[] TEST_SUITE_TEXT_FILE_EXTENSIONS = new String[] { ".txt" };
	public static final String[] TEST_SUITE_FILE_EXTENSIONS = new String[] { ".htm", ".html", ".xhtml", ".txt" };
	public static final String TEST_SUITE_DEFAULT_TEXT_FILE_EXTENSION = ".txt";
	public static final String TEST_SUITE_DEFAULT_HTML_FILE_EXTENSION = ".html";

	@Restrict(@Group(Application.ROLE_USER))
	public static Result listTestSuites(long projectId) {
		Project project = Project.findById(projectId);
		List<Resource> testSuites = Resource.findResourcesByProject(project, ResourceType.TestSuite);
		return ok(list.render(projectId, testSuites));
	}

	@Restrict(@Group(Application.ROLE_USER))
	public static Result addTestSuite(long projectId) {
		Form<Resource> form = new Form<Resource>(Resource.class);
		form.data().put("test-suite-content", testSuiteService.getTestSuiteContentTemplate());
		return ok(add.render(projectId, form));
	}

	@Restrict(@Group(Application.ROLE_USER))
	public static Result createTestSuite(long projectId) {
		Form<Resource> form = Form.form(Resource.class).bindFromRequest();
		if (!form.hasErrors()) {
			Resource testSuite = form.get();
			testSuite.setCreatedDate(new Date());
			testSuite.setLastModifiedDate(testSuite.getCreatedDate());
			testSuite.setProject(Project.findById(projectId));
			testSuite.setType(ResourceType.TestSuite);

			String testSuiteContent = Form.form().bindFromRequest().get("test-suite-content");

			testSuiteService.saveTestSuite(testSuite, testSuiteContent);

			return redirect(controllers.routes.TestSuites.editTestSuite(projectId, testSuite.getId()));
		} else {
			return ok(add.render(projectId, form));
		}
	}

	// GET
	@Restrict(@Group(Application.ROLE_USER))
	public static Result editTestSuite(long projectId, long testSuiteId) {
		Resource testSuite = testSuiteService.getTestSuite(testSuiteId);
		if (testSuite != null) {
			Form<Resource> form = Form.form(Resource.class).fill(testSuite);
			form.data().put("test-suite-content", testSuiteService.getTestSuiteFileContent(testSuite));
			return ok(edit.render(testSuite, form));
		} else {
			return redirect(controllers.routes.TestSuites.listTestSuites(projectId));
		}
	}

	// POST
	@Restrict(@Group(Application.ROLE_USER))
	public static Result updateTestSuite(long projectId, long testSuiteId) {
		Resource testSuite = testSuiteService.getTestSuite(testSuiteId);
		if (testSuite != null) {
			Resource newTestSuite = Form.form(Resource.class).bindFromRequest().get();
			testSuite.setName(newTestSuite.getName());
			testSuite.setDescription(newTestSuite.getDescription());

			String testSuiteContent = Form.form().bindFromRequest().get("test-suite-content");

			testSuiteService.saveTestSuite(testSuite, testSuiteContent);

			return redirect(controllers.routes.TestSuites.editTestSuite(projectId, testSuiteId));
		} else {
			return redirect(controllers.routes.TestSuites.listTestSuites(projectId));
		}
	}

	@Restrict(@Group(Application.ROLE_USER))
	public static Result runTestsWithDefaultConfig(long projectId, long testSuiteId) {
		Resource config = Project.findById(projectId).getDefaultConfiguration();
		return runTestsWithConfig(projectId, testSuiteId, config);
	}

	@Restrict(@Group(Application.ROLE_USER))
	public static Result runTestsWithConfig(long projectId, long testSuiteId, long configId) {
		Resource config = configurationService.getConfig(configId);
		return runTestsWithConfig(projectId, testSuiteId, config);
	}

	private static Result runTestsWithConfig(long projectId, long testSuiteId, Resource config) {
		Project project = Project.findById(projectId);
		Resource testSuite = testSuiteService.getTestSuite(testSuiteId);
		if (project == null || testSuite == null || config == null) {
			return redirect(controllers.routes.TestSuites.listTestSuites(projectId));
		}
		String content = "";
		try {
			String testSuiteName = testSuite.getName();
			if (ConsoleUtils.hasFileExtension(testSuiteName, TEST_SUITE_FILE_EXTENSIONS)) {
				testSuiteName = testSuiteName.substring(0, testSuiteName.lastIndexOf('.'));
			}
			URL url = testSuiteService.getTestExecuteURL(project, testSuiteName, config);

			URLConnection connection = url.openConnection();
			BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
			String line;
			while ((line = in.readLine()) != null) {
				content += line + "\n";
			}
			in.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (URISyntaxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return ok(content);
	}

	// GET
	@Restrict(@Group(Application.ROLE_USER))
	public static Result stepCreateTestSuite(long projectId, int step) {
		if (step == 1) {
			return handleStepCreateTestSuiteStep1(projectId);
		} else if (step == 2) {
			return handleStepCreateTestSuiteStep2(projectId);
		} else if (step == 3) {
			return handleStepCreateTestSuiteStep3(projectId);
		} else if (step == 4) {
			return handleStepCreateTestSuiteStep4(projectId);
		} else {
			return badRequest("Invalid step: " + step);
		}
	}

	private static Result handleStepCreateTestSuiteStep1(long projectId) {
		Form<TestSuiteWizard.Step1> form = Form.form(TestSuiteWizard.Step1.class);
		return ok(step1.render(projectId, form));
	}

	private static Result handleStepCreateTestSuiteStep2(long projectId) {
		Form<TestSuiteWizard.Step2> form = Form.form(TestSuiteWizard.Step2.class);
		Company company = ConsoleUtils.getCompany();
		List<Resource> resources = Resource.findResourcesByCompany(company, ResourceType.TestResource);
		return ok(step2.render(projectId, form, resources));
	}

	private static Result handleStepCreateTestSuiteStep3(long projectId) {
		Form<TestSuiteWizard.Step3> form = Form.form(TestSuiteWizard.Step3.class);
		HashMap<String, String> settings = new HashMap<String, String>();
		settings.put("", "");
		List<String> preDefinedSettings = new ArrayList<String>();
		return ok(step3.render(projectId, form, settings, preDefinedSettings));
	}

	private static Result handleStepCreateTestSuiteStep4(long projectId) {
		// DynamicForm form = new DynamicForm();
		return TODO;
	}

	// POST
	@Restrict(@Group(Application.ROLE_USER))
	public static Result submitCreateTestSuite(long projectId, int step) {
		StepDirection direction = getDirection(request().body().asFormUrlEncoded());
		if (step == 1) {
			return handleSubmitCreateTestSuiteStep1(projectId, direction);
		} else if (step == 2) {
			return handleSubmitCreateTestSuiteStep2(projectId, direction);
		} else if (step == 3) {
			return handleSubmitCreateTestSuiteStep3(projectId, direction);
		} else if (step == 4) {
			return handleSubmitCreateTestSuiteStep4(projectId, direction);
		} else {
			return badRequest("Invalid step: " + step);
		}
	}

	private static StepDirection getDirection(Map<String, String[]> parameters) {
		StepDirection direction = StepDirection.Next;
		if (parameters != null) {
			String actions[] = parameters.get("action");
			String action;
			if (actions != null && actions.length > 0) {
				action = actions[0];
				if ("next".equalsIgnoreCase(action)) {
					direction = StepDirection.Next;
				} else if ("previous".equalsIgnoreCase(action)) {
					direction = StepDirection.Previous;
				}
			}
		}
		return direction;
	}

	private static Result handleSubmitCreateTestSuiteStep1(long projectId, StepDirection direction) {
		Form<TestSuiteWizard.Step1> form = Form.form(TestSuiteWizard.Step1.class).bindFromRequest();
		if (!form.hasErrors()) {
			storeInSession(form.get());
			return redirect(routes.TestSuites.stepCreateTestSuite(projectId, 2));
		} else {
			return badRequest(step1.render(projectId, form));
		}
	}

	private static Result handleSubmitCreateTestSuiteStep2(long projectId, StepDirection direction) {
		if (direction == StepDirection.Next) {
			Form<TestSuiteWizard.Step2> form = Form.form(TestSuiteWizard.Step2.class).bindFromRequest();
			if (!form.hasErrors()) {
				storeInSession(form.get());
				return redirect(routes.TestSuites.stepCreateTestSuite(projectId, 3));
			} else {
				Company company = ConsoleUtils.getCompany();
				List<Resource> resources = Resource.findResourcesByCompany(company, ResourceType.TestResource);
				return ok(step2.render(projectId, form, resources));
			}
		} else if (direction == StepDirection.Previous) {
			Step1 step = restoreStep1FromSession();
			Form<TestSuiteWizard.Step1> form = Form.form(TestSuiteWizard.Step1.class).fill(step);
			return ok(step1.render(projectId, form));
		} else {
			return badRequest("Invalid direction: " + direction);
		}
	}

	private static Result handleSubmitCreateTestSuiteStep3(long projectId, StepDirection direction) {
		if (direction == StepDirection.Next) {
			Form<TestSuiteWizard.Step3> form = Form.form(TestSuiteWizard.Step3.class).bindFromRequest();
			DynamicForm customForm = Form.form().bindFromRequest();
			if (!form.hasErrors() && !customForm.hasErrors()) {
				storeInSession(form.get(), customForm.data());
				return redirect(routes.TestSuites.stepCreateTestSuite(projectId, 4));
			} else {
				HashMap<String, String> settings = new HashMap<String, String>();
				String index;
				for (String key : customForm.data().keySet()) {
					if (key.startsWith("setting-key-")) {
						index = key.substring(key.lastIndexOf('-') + 1);
						settings.put(customForm.data().get(key), customForm.data().get("setting-value-" + index));
					}
				}
				List<String> preDefinedSettings = new ArrayList<String>();
				return ok(step3.render(projectId, form, settings, preDefinedSettings));
			}
		} else if (direction == StepDirection.Previous) {
			Step2 step = restoreStep2FromSession();
			Form<TestSuiteWizard.Step2> form = Form.form(TestSuiteWizard.Step2.class).fill(step);
			Company company = ConsoleUtils.getCompany();
			List<Resource> resources = Resource.findResourcesByCompany(company, ResourceType.TestResource);
			return ok(step2.render(projectId, form, resources));
		} else {
			return badRequest("Invalid direction: " + direction);
		}
	}

	private static Result handleSubmitCreateTestSuiteStep4(long projectId, StepDirection direction) {
		// DynamicForm form = Form.form().bindFromRequest();
		return TODO;
	}

	private static void storeInSession(Step1 step1) {
		if (step1 != null) {
			session().put("testsuite.wizard.step1.name", step1.getName());
			session().put("testsuite.wizard.step1.description", step1.getDescription());
		}
	}

	private static Step1 restoreStep1FromSession() {
		Step1 step = new Step1();
		Session session = session();
		if (session.containsKey("testsuite.wizard.step1.name")) {
			step.setName(session.get("testsuite.wizard.step1.name"));
		}
		if (session.containsKey("testsuite.wizard.step1.description")) {
			step.setDescription(session.get("testsuite.wizard.step1.description"));
		}
		return step;
	}

	private static void storeInSession(Step2 step2) {
		if (step2 != null) {
			session().put("testsuite.wizard.step2.resources", StringUtils.join(step2.getResources(), "|"));
		}
	}

	private static Step2 restoreStep2FromSession() {
		Step2 step = new Step2();
		Session session = session();
		if (session.containsKey("testsuite.wizard.step2.resources")) {
			for (String resource : StringUtils.split(session.get("testsuite.wizard.step2.resources"), "|")) {
				step.getResources().add(resource);
			}
		}
		return step;
	}

	@SuppressWarnings("unchecked")
	private static void storeInSession(Step3 step3, Map<String, String> customData) {
		if (step3 != null) {
			session().put("testsuite.wizard.step3.settings", StringUtils.join(step3.getSettings()));
		}
		if (customData != null) {
			for (String key : customData.keySet()) {
				if (key.startsWith("setting-")) {
					session().put("testsuite.wizard.step3." + key, customData.get(key));
				}
			}
		}
	}

}
